﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Common
{
    public class HtmlNode
    {
        public int Level { get; set; }
    }
    public class HtmlTextNode : HtmlNode
    {
        public string Text { get; set; }

        public override string ToString()
        {
            return Text;
        }
    }

    public class HtmlAttrNode
    {
        public string Name { get; set; }
        public bool IsSole { get; set; }
        public string Value { get; set; }

        public override string ToString()
        {
            if (IsSole && Value.HasValue() == false)
            {
                return Name;
            }
            else return string.Format(@"{0}={1}", Name,
                Value[0].ToString().IsIn("'", @"""") && Value[0] == Value[Value.Length - 1] ?
                Value : @"""" + Value + @""""
                );
        }
    }
    public class HtmlTagNode : HtmlNode
    {
        public HtmlTagNode() { IsSole = false; Attrs = new List<HtmlAttrNode>(); }
        public bool IsSole { get; set; }
        public string TagName { get; set; }
        public List<HtmlAttrNode> Attrs { get; set; }

        public override string ToString()
        {
            string retVal = "";
            if (Attrs.Count == 0)
            {
                retVal = string.Format(@"<{0}", TagName);
            }
            else
            {
                retVal = string.Format(@"<{0} {1}", TagName,
                    string.Join(" ", Attrs.Select(o => o.ToString()).ToArray()));
            }

            if (IsSole)
            {
                retVal += "/>";
            }
            else retVal += ">";

            return retVal;
        }
    }
    public class HtmlCloseTagNode : HtmlNode
    {

        public string TagName { get; set; }

        public override string ToString()
        {
            return string.Format(@"</{0}>", TagName);
        }
    }

    public class HtmlParse
    {
        private List<string> Sects = null;
        public List<HtmlNode> Load(string Html)
        {
            List<HtmlNode> retVal = new List<HtmlNode>();

            LoadToSect(Html);

            int curLevel = 0;

            int start = 0;
            ////判断是否是被截断的输出流
            //int cutedClosedIndex = GetCutedClosedTagIndex(start);

            //if (cutedClosedIndex > 0)
            //{
            //    HtmlTextNode txt = new HtmlTextNode();

            //    txt.Level = 1;
            //    txt.Text = string.Join("", Sects.GetSub(start, cutedClosedIndex));
            //    retVal.Add(txt);

            //    start = cutedClosedIndex - 1;
            //}

            for (int i = start; i < Sects.Count; i++)
            {
                int beginTagIndex = GetNextMarkIndex(i);

                if (retVal.Count > 0 && retVal[retVal.Count - 1] is HtmlTagNode)
                {
                    string tagName = (retVal[retVal.Count - 1] as HtmlTagNode).TagName;
                    if (tagName.IsIn(StringComparer.CurrentCultureIgnoreCase,
                    "script", "style", "pre", "textarea"))
                    {
                        int endTagIndex = GetCloseTag(beginTagIndex, tagName);

                        HtmlTextNode txt = new HtmlTextNode();

                        txt.Level = curLevel + 1;

                        txt.Text = string.Join("", Sects.GetSub(i, beginTagIndex).ToArray());

                        txt.Text += string.Join("", Sects.GetSub(beginTagIndex, endTagIndex));

                        retVal.Add(txt);
                        i = endTagIndex - 1;
                        continue;
                    }
                }


                if (i != beginTagIndex)
                {
                    HtmlTextNode txt = new HtmlTextNode();

                    txt.Level = curLevel + 1;

                    txt.Text = string.Join("", Sects.GetSub(i, beginTagIndex).ToArray());

                    retVal.Add(txt);
                    i = beginTagIndex - 1;
                    continue;
                }

                if (Sects[beginTagIndex] == "<")
                {
                    HtmlTagNode tag = new HtmlTagNode();

                    tag.Level = curLevel + 1;
                    tag.TagName = Sects[beginTagIndex + 1];

                    int endTagIndex = GetNextMarkIndex(beginTagIndex + 2);
                    if (Sects[endTagIndex] == @"/>")
                    {
                        tag.IsSole = true;
                        curLevel--;
                    }

                    HtmlAttrNode an = null;
                    for (int j = beginTagIndex + 2; j < endTagIndex; j++)
                    {
                        if (Sects[j].Trim().HasValue() == false)
                        {
                            continue;
                        }
                        if (an == null)
                        {
                            an = new HtmlAttrNode();
                            an.IsSole = true;
                            an.Name = Sects[j];
                            tag.Attrs.Add(an);
                            continue;
                        }
                        else if (Sects[j] == "=")
                        {
                            an.IsSole = false;
                            continue;
                        }
                        else
                        {
                            if (an.IsSole == true)
                            {
                                an = new HtmlAttrNode();
                                an.IsSole = true;
                                an.Name = Sects[j];
                                tag.Attrs.Add(an);
                                continue;
                            }
                            else
                            {
                                an.Value = Sects[j];
                                an = null;
                            }
                        }
                    }

                    retVal.Add(tag);
                    i = endTagIndex;

                    curLevel++;
                }
                else if (Sects[beginTagIndex] == "</" && IsColseTag(beginTagIndex, ""))
                {
                    HtmlCloseTagNode cl = new HtmlCloseTagNode();
                    cl.TagName = Sects[beginTagIndex + 1];

                    cl.Level = curLevel;

                    retVal.Add(cl);

                    i = beginTagIndex + 2;
                    curLevel--;
                }
                else
                {
                    HtmlTextNode txt = new HtmlTextNode();

                    txt.Level = curLevel + 1;

                    txt.Text = string.Join("", Sects.GetSub(i, beginTagIndex).ToArray());
                    retVal.Add(txt);
                }

            }


            for (int i = 0; i < retVal.Count; i++)
            {
                if (retVal[i] is HtmlTextNode)
                {
                    bool IsNormalText = true;
                    HtmlTextNode txt = retVal[i] as HtmlTextNode;

                    if (i > 0)
                    {
                        if (retVal[i - 1] is HtmlTagNode)
                        {
                            if ((retVal[i - 1] as HtmlTagNode).TagName.IsIn(StringComparer.CurrentCultureIgnoreCase,
                                "pre", "textarea"))
                            {
                                IsNormalText = false;
                            }
                        }
                    }

                    if (IsNormalText) txt.Text = txt.Text.Trim();

                    if (txt.Text.HasValue() == false)
                    {
                        retVal.RemoveAt(i);
                        i--;
                    }
                }
            }
            return retVal;
        }

        private static int GetNextClose(List<string> Sects, string CloseString, int Start)
        {
            for (int i = Start; i < Sects.Count; i++)
            {
                if (Sects[i] == CloseString)
                {
                    if (i > 0 && Sects[i - 1] != @"\")
                    {
                        return i;
                    }
                }
            }
            return Sects.Count - 1;
        }

        private static int GetNextClose(string Content, char CloseTag, int Start)
        {
            for (int i = Start; i < Content.Length; i++)
            {
                if (Content[i] == CloseTag)
                {
                    if (i > 0 && Content[i - 1] != '\\')
                    {
                        return i;
                    }
                }
            }
            return Content.Length - 1;
        }

        private static bool InString(Dictionary<int, int> DictSored, int TestIndex)
        {
            foreach (var item in DictSored)
            {
                if (item.Value < TestIndex)
                {
                    return false;
                }
                else if (item.Key > TestIndex && item.Value < TestIndex)
                {
                    return true;
                }
            }
            return false;
        }
        private static int FindWithoutRef(string Content, string Find, Dictionary<int, int> RefDict, int Start)
        {
            int pos = Start;
            while (true)
            {
                if (pos > Content.Length - 1) break;
                pos = Content.IndexOf(Find, pos);
                if (pos < 0) return -1;
                if (InString(RefDict, pos) == false) return pos;
                else pos++;
            }
            return -1;
        }

        public static string FormatJs(string JsContent)
        {
            return JsContent;
        }
        public static string FormatCss(string CssContent)
        {
            return CssContent;
        }

        private int GetCloseTag(int start, string tagName)
        {
            int endTagIndex = start;
            while (true)
            {
                endTagIndex = GetNextMarkIndex(endTagIndex);
                if (IsColseTag(endTagIndex, tagName))
                {
                    return endTagIndex;
                }
                else endTagIndex++;

                if (endTagIndex >= Sects.Count) return Sects.Count;
            }
        }

        /// <summary>
        /// 是否是 关闭标签。
        /// </summary>
        /// <param name="Sects"></param>
        /// <param name="endTagIndex"></param>
        /// <param name="TagName"></param>
        /// <returns></returns>
        private bool IsColseTag(int endTagIndex, string TagName)
        {
            if (endTagIndex < Sects.Count && Sects[endTagIndex] == "</")
            {
                if (endTagIndex + 1 < Sects.Count && Sects[endTagIndex + 1].Trim().HasValue() == false) Sects.RemoveAt(endTagIndex + 1);

                if (endTagIndex + 2 < Sects.Count && Sects[endTagIndex + 2].Trim().HasValue() == false) Sects.RemoveAt(endTagIndex + 2);

                if (endTagIndex + 2 < Sects.Count && Sects[endTagIndex + 2] == ">")
                {
                    if (TagName.HasValue() == false) return true;

                    if (Sects[endTagIndex + 1].Equals(TagName, StringComparison.CurrentCultureIgnoreCase))
                    {
                        return true;
                    }
                }
            }
            //if (endTagIndex > 0 && Sects[endTagIndex] == "/>") return true;

            return false;
        }

        private int GetNextMarkIndex(int start)
        {
            for (int i = start; i < Sects.Count; i++)
            {
                if (Sects[i].IsIn("<", "/>", "</", ">")) return i;
            }
            return Sects.Count;
        }

        /// <summary>
        /// 1.分词，2.合并文本，3.把 /&rt; 以及 &rt;/  合并。 
        /// 所以 &lt 一定是一个开始标记。
        /// </summary>
        /// <param name="Html"></param>
        private void LoadToSect(string Html)
        {
            Sects = new List<string>();

            //1.分词。
            MatchCollection mc = Regex.Matches(Html, @"""|'|<|>|\b|/", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);

            int pos = 0;
            for (int i = 0; i < mc.Count; i++)
            {
                Match m = mc[i];
                //if (i == 0)
                //{
                //    Sects.Add(Html.Substring(pos, m.Index));
                //    if (m.Value.HasValue()) Sects.Add(m.Value);
                //}
                //else
                {
                    Sects.Add(Html.Substring(pos, m.Index - pos));
                    if (m.Value.HasValue()) Sects.Add(m.Value);

                    if (i == mc.Count - 1)
                    {
                        Sects.Add(Html.Substring(m.Index + m.Length));
                    }
                }

                pos = m.Index + m.Length;
            }

            //2.去空。
            Sects = Sects.Where(o => o.HasValue()).ToList();

            //3. 合并文本。
            string beginChar = "";

            int start = 0;
            for (int i = 0; i < Sects.Count; i++)
            {
                if (Sects[i] == "<" && Sects[i + 1] == "!--")
                {
                    Sects[i] += Sects[i + 1];
                    Sects.RemoveAt(i + 1);


                    //上面之后 Sect[i] 变为：<!-- 
                    while (true)
                    {
                        if ((i + 2 < Sects.Count) == false) break;

                        if (Sects[i + 2] == ">" && Sects[i + 1].EndsWith("--"))
                        {
                            Sects[i] += Sects[i + 1];
                            Sects.RemoveAt(i + 1);

                            Sects[i] += Sects[i + 1];
                            Sects.RemoveAt(i + 1);
                            break;
                        }
                        else
                        {
                            Sects[i] += Sects[i + 1];
                            Sects.RemoveAt(i + 1);
                        }
                    }

                    continue;
                }
                else if (Sects[i] == "'" || Sects[i] == @"""")
                {
                    if (i > 0 && Sects[i - 1][Sects[i - 1].Length - 1] == '\\') continue;

                    if (beginChar == Sects[i])
                    {
                        beginChar = "";

                        for (int j = start; j < i; j++)
                        {
                            Sects[start] += Sects[start + 1];
                            Sects.RemoveAt(start + 1);
                        }
                        i = start + 1;
                    }
                    else if (beginChar == "")
                    {
                        start = i;
                        beginChar = Sects[i];
                    }
                    continue;
                }
            }

            //合并结束标记 /> </ . 保证 <是开始标记。 >可能是是 打开和关闭的 结束标记。
            for (int i = 0; i < Sects.Count; i++)
            {
                //<![CDATA[     ]]>
                if (Sects[i] == "<" && Sects[i + 1] == "![" && Sects[i + 3][0] == '[' &&
                    Sects[i + 2].Equals("CDATA", StringComparison.CurrentCultureIgnoreCase))
                {
                    Sects[i] += Sects[i + 1];
                    Sects.RemoveAt(i + 1);

                    Sects[i] += Sects[i + 1];
                    Sects.RemoveAt(i + 1);

                    Sects[i] += Sects[i + 1];
                    Sects.RemoveAt(i + 1);

                    while (true)
                    {
                        if ((i + 2 < Sects.Count) == false) break;

                        if (Sects[i + 2] == ">" && Sects[i + 1].EndsWith("]]"))
                        {
                            Sects[i] += Sects[i + 1];
                            Sects.RemoveAt(i + 1);

                            Sects[i] += Sects[i + 1];
                            Sects.RemoveAt(i + 1);
                            break;
                        }
                        else
                        {
                            Sects[i] += Sects[i + 1];
                            Sects.RemoveAt(i + 1);
                        }
                    }
                }
                // <!doctype>
                else if (Sects[i] == "<" && Sects[i + 1] == "!" && Sects[i + 2] != "--")
                {
                    Sects[i] += Sects[i + 1];
                    Sects.RemoveAt(i + 1);
                    while (true)
                    {
                        if ((i + 1 < Sects.Count) == false) break;

                        if (Sects[i + 1] == ">")
                        {
                            Sects[i] += Sects[i + 1];
                            Sects.RemoveAt(i + 1);
                            break;
                        }
                        else
                        {
                            Sects[i] += Sects[i + 1];
                            Sects.RemoveAt(i + 1);
                        }
                    }
                    continue;
                }
                else if (Sects[i] == "/" && Sects[i + 1] == ">")
                {
                    Sects[i] += Sects[i + 1];
                    Sects.RemoveAt(i + 1);
                    continue;
                }
                else if (Sects[i] == "<" && Sects[i + 1] == "/")
                {
                    Sects[i] += Sects[i + 1];
                    Sects.RemoveAt(i + 1);
                    continue;
                }
                else if (Sects[i] == "<" && char.IsLetter(Sects[i + 1][0]) == false)
                {
                    Sects[i] += Sects[i + 1];
                    Sects.RemoveAt(i + 1);
                    continue;
                }
            }
        }
    }
}
